/**
 * DateUtil.java
 */
package com.mht2017.common.utils;

import java.text.DateFormat;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.List;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;

import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

/**
 * @author yutong.xiao
 * @date 2017年6月22日 下午3:02:45
 */
public class DateUtil {

	private static final Logger LOGGER = LoggerFactory.getLogger(DateUtil.class);

	private static final long MILLS_RATE = 1000;
	private static final long SECOND_RATE = 60;
	private static final long MINUTE_RATE = 60;
	private static final long HOUR_RATE = 24;
	private static final String DEFAULT_CACHE_NAME = "defaultCacheName";

	private static final ThreadLocal<DateFormat> YYYY_MM_DD_HH_MM_SS_SSS = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS"));
	private static final ThreadLocal<DateFormat> YYYY_MM_DD_HH_MM_SS = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"));
	private static final ThreadLocal<DateFormat> YYYY_MM_DD = ThreadLocal.withInitial(() -> new SimpleDateFormat("yyyy-MM-dd"));
	private static final ThreadLocal<DateFormat> HH_MM_SS = ThreadLocal.withInitial(() -> new SimpleDateFormat("HH:mm:ss"));
	private static final List<ThreadLocal<DateFormat>> FORMATERS = Arrays.asList(YYYY_MM_DD_HH_MM_SS_SSS, YYYY_MM_DD_HH_MM_SS, YYYY_MM_DD, HH_MM_SS);
	private static Map<String, ThreadLocal<DateFormat>> lastSuccessedFormaterMap = new ConcurrentHashMap<>();

	public static Date parse(final String source) {
		return parse(source, null);
	}

	public static Date parse(String source, boolean useCache) {
		return parse(source, useCache ? DEFAULT_CACHE_NAME : null);
	}

	/**
	 *
	 * @param source
	 * @param useCache
	 *            是否使用缓存(上次parse成功的格式)，请谨慎使用缓存，一般只有在所有的日期格式一致的时候才使用。<br/>
	 *            潜在的问题：如果第一个格式只有年月日没有时分秒，而第二个有时分秒，则第二个日期的时分秒会被忽略
	 * @return
	 */
	public static Date parse(String source, String cacheName) {
		if (StringUtils.isEmpty(source)) {
			return null;
		}

		boolean useCache = StringUtils.isNotEmpty(cacheName);
		ThreadLocal<DateFormat> lastSuccessedFormater = useCache ? lastSuccessedFormaterMap.get(cacheName) : null;
		if (useCache && null != lastSuccessedFormater) {
			try {
				return lastSuccessedFormater.get().parse(source);
			} catch (ParseException ignored) {
			}
		}
		for (final ThreadLocal<DateFormat> format : FORMATERS) {
			if (useCache && format == lastSuccessedFormater) {
				continue;
			}
			try {
				Date parse = format.get().parse(source);
				if (useCache) {
					lastSuccessedFormaterMap.put(cacheName, lastSuccessedFormater = format);
				}
				return parse;
			} catch (ParseException ignored) {
			}
		}
		LOGGER.error(String.format("failed to parse '%s' to date.", source), new Exception());
		return null;
	}

	public static String toSqlDateFormat(Date date) {
		if (null == date) {
			return null;
		}

		return YYYY_MM_DD_HH_MM_SS.get().format(date);
	}

	public static String toLocalDateFormat(Date date) {
		if (null == date) {
			return null;
		}

		return YYYY_MM_DD_HH_MM_SS.get().format(date);
	}

	public static String toHumanString(Date startDate, Date endDate) {
		if (null == startDate || null == endDate) {
			return "<empty>";
		}

		return toHumanString(endDate.getTime() - startDate.getTime());
	}

	public static String toHumanString(long deltaInMills) {
		long mills = deltaInMills % MILLS_RATE;
		deltaInMills /= MILLS_RATE;
		long second = deltaInMills % SECOND_RATE;
		deltaInMills /= SECOND_RATE;
		long minute = deltaInMills % MINUTE_RATE;
		deltaInMills /= MINUTE_RATE;
		long hour = deltaInMills % HOUR_RATE;
		deltaInMills /= HOUR_RATE;
		long day = deltaInMills; // 剩下的全部按天计算
		StringBuilder sb = new StringBuilder();
		if (day > 0) {
			sb.append(day);
			sb.append("d");
		}
		if (hour > 0) {
			sb.append(hour);
			sb.append("h");
		}
		if (minute > 0) {
			sb.append(minute);
			sb.append("min");
		}
		if (second > 0) {
			sb.append(second);
			sb.append("s");
		}
		if (mills > 0) {
			sb.append(mills);
			sb.append("ms");
		}
		if (sb.length() <= 0) {
			sb.append("0ms");
		}
		return sb.toString();
	}

}
